package jamirr;

import java.lang.annotation.Annotation;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

public final class AnnotationMirrorBuilder extends AnnotatedElementBuilder<AnnotationMirrorBuilder> {

    private static final AnnotationMirror[] EMPTY_ANNOTATION_ARRAY = new AnnotationMirror[0];

    private final String name;
    private Map<String, AnnotationAttributeBuilder<?>> attributeBuilders;

    AnnotationMirrorBuilder(String name) {
        this.name = name;
    }

    public AnnotationMirrorBuilder declareAttribute(AnnotationAttributeBuilder<?> attribute) {
        Objects.requireNonNull(attribute, "attribute cannot be null");
        if (attributeBuilders == null) {
            attributeBuilders = new LinkedHashMap<>();
        }
        attributeBuilders.put(attribute.getName(), attribute);
        return this;
    }

    @SuppressWarnings("rawtypes")
    private AnnotationAttributeBuilder getAttribute(String name) {
        if (attributeBuilders == null) {
            return null;
        }
        return attributeBuilders.get(name);
    }

    private Map<String, AnnotationAttribute<?>> buildAttributes() {
        if (attributeBuilders == null) {
            return Map.of();
        }
        return attributeBuilders.values()
                .stream()
                .map(b -> Map.entry(b.getName(), b.build()))
                .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    private AnnotationMirror[] buildAnnotations() {
        return declaredAnnotations == null
                ? EMPTY_ANNOTATION_ARRAY
                : declaredAnnotations.toArray(EMPTY_ANNOTATION_ARRAY);
    }

    public AnnotationMirror build(Object... input) {
        return build(new MapN<>(input));
    }

    @SuppressWarnings({"unchecked"})
    public AnnotationMirror build(Map<String, Object> values) {
        Objects.requireNonNull(values, "values cannot be null");
        values.forEach((name, value) -> {
            var attr = getAttribute(name);
            if (attr != null) {
                attr.setValue(value);
            }
        });

        return new AnnotationMirrorImpl(name, buildAttributes(), buildAnnotations());
    }

    public static AnnotationMirrorBuilder load(Class<? extends Annotation> annotationType) {
        Objects.requireNonNull(annotationType, "annotationType cannot be null");
        return load(annotationType.getName());
    }

    public static AnnotationMirrorBuilder load(String annotationName) {
        AnnotationReference reference = AnnotationReferenceLoader.INSTANCE.getInstance(annotationName);
        return Optional.ofNullable(reference)
                .map(AnnotationReference::load)
                .orElse(null);
    }
}
